import java.io.BufferedWriter;

import java.io.File;
import java.io.FileWriter;
import java.io.FileReader;
import java.io.IOException;
import java.io.FileNotFoundException;
import java.util.Random;
import java.util.Set;

import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.CmdLineParser;
import org.kohsuke.args4j.Option;

import problem.CAPM;
import problem.Markowitz;
import problem.ProblemZDT1;
import problem.ProblemZDT2;
import problem.ProblemZDT3;
import problem.ProblemZDT4;
import problem.ProblemZDT6;
import problem.Sharpe;
import core.IEnvironment;
import core.Problem;
import core.Result;
import core.evolutionary.Agent;
import core.evolutionary.Edge;
import core.evolutionary.ElemasAgent;
import core.evolutionary.EvolutionaryEnvironment;
import core.evolutionary.Island;
import core.simple.NSGA2Environment;
import core.simple.SPEA2Environment;


public class Jvolution {
	@Option(name="--emas",usage="Algorytm EMAS - tylko jeden algorytm będzie uruchomiony przy danym uruchomieniu programu w hierarchii EMAS, ElEMAS, SPEA2, NSGA II")
    private static boolean emas = false;
	@Option(name="--elemas",usage="Algorytm ElEMAS - tylko jeden algorytm będzie uruchomiony przy danym uruchomieniu programu w hierarchii EMAS, ElEMAS, SPEA2, NSGA II")
    private static boolean elemas = false;
	@Option(name="--spea",usage="Algorytm SPEA2 - tylko jeden algorytm będzie uruchomiony przy danym uruchomieniu programu w hierarchii EMAS, ElEMAS, SPEA2, NSGA II")
    private static boolean spea2 = false;
	@Option(name="--nsga",usage="Algorytm NSGA II - tylko jeden algorytm będzie uruchomiony przy danym uruchomieniu programu w hierarchii EMAS, ElEMAS, SPEA2, NSGA II")
    private static boolean nsgaii = false;
    @Option(name="--problem",usage="Nazwa rozwiazywanego probemu")        
    private static String problem = "ZDT1";
    @Option(name="--steps",usage="Ilosc krokow")
    private static int steps = 200;
    @Option(name="--cost",usage="Koszt przejscia po krawedzi")
    private static int edge_cost = 1;
    @Option(name="--hvr-intervals",usage="Ilosc interwalow z ktorych liczony jest HVR i robiony gnuplot")
    private static int stepIntervals = 10;
    @Option(name="--normal-islands",usage="Ilosc wysp nieelitarnych")
    private static int islands = 1;
    @Option(name="--elitist-islands",usage="Ilosc wysp elitarnych")
    private static int elitistIslands = 1;
    @Option(name="--agents-per-island",usage="Ilosc agentow na jednej wyspie na poczatku")
    private static int agents = 200;
    @Option(name="--agents-per-elitist-island",usage="Ilosc agentow na jednej elitarnej wyspie na poczatku")
    private static int elitistAgents = 0;
    @Option(name="--distance",usage="Odleglosc dla bliskich spotkan (tylko ElEMAS)")
    private static double distance = 1.5;
    @Option(name="--energy-transfer",usage="Ilosc energi przenoszona przy dominacji")
    private static int transferableEnergy = 2;
    @Option(name="--elitarity-transfer",usage="Ilosc elitarnosci pozyskiwana przy dominacji")
    private static int transferableElitarity = 1;
    @Option(name="--genotype-length",usage="Dlugosc genotypu")
    private static int genotypeLength = 5;
    @Option(name="--when-to-die",usage="Kiedy umrzec")
    private static int whenToDie = 0;
    @Option(name="--when-to-reproduce",usage="Kiedy reprodukowac sie")
    private static int whenToReproduce = 14;
    @Option(name="--when-to-become-elitist",usage="Ilosc elitarnosci potrzebna dla stania sie elitarnym (tylko ElEMAS)")
    private static int becomeElitist = 17;
    @Option(name="--initial-life-energy",usage="Poczatkowy poziom energii zyciowej")
    private static int initialLifeEnergyLevel = 6;
    @Option(name="--initial-elitarity",usage="Poczatkowy poziom elitarnosci")
    private static int initialElitarityLevel = 0;
    @Option(name="--eta_m",usage="Wartosc eta_m przy mutacji")
    private static double mutationParam = 2.;
    @Option(name="--action-probability",usage="Prawdopodobienstwo podejmowania akcji")
    private static double actionsPropability = 0.95;
    //NSGA&SPEA
    @Option(name="--population-size",usage="rozmiar populacji")
    private static int sizeOfPopulation = 250;
    @Option(name="--output-size",usage="maksymalny rozmiar zbioru zewnetrznego")
    private static int sizeOfArchieve = 400;
    @Option(name="--crossover-param",usage="Wartosc parametru przy krzyzowaniu")
    private static double crossoverParam = 2.;
    @Option(name="--mutation-probability",usage="Prawdopodobienstwo mutacji")
    private static double mutationProbability = .3;
    @Option(name="--data-filename",usage="Plik z danymi modelu")
    private static String filename = "sharpe.txt";
    @Option(name="--capital",usage="Kapital zainwestowany w weryfikacji algorytmow")
    private static Double capital = 10000.;
    @Option(name="--analysis-number",usage="Ilo�� analizowanych zakup�w akcji")
    private static Integer analysisNumber = 10;
	// receives other command line parameters than options
//    @Argument
//    private List<String> arguments = new ArrayList<String>();

    
	/**
	 * @param args
	 * @throws Exception 
	 */
	public static void main(String[] args) throws Exception {
		new File("data").mkdirs();
		new Jvolution().parse(args);
		
		Problem problemObj = null;
		if(problem.equalsIgnoreCase("zdt1")) {
			problemObj = new ProblemZDT1();
		} 
		else if(problem.equalsIgnoreCase("zdt2")) {
			problemObj = new ProblemZDT2();
		} else if(problem.equalsIgnoreCase("zdt3")) {
			problemObj = new ProblemZDT3();
		} else if(problem.equalsIgnoreCase("zdt4")) {
			problemObj = new ProblemZDT4();
		} else if(problem.equalsIgnoreCase("zdt6")) {
			problemObj = new ProblemZDT6();
		} else if(problem.equalsIgnoreCase("markowitz")) {
			//sprawdzenie istnienia pliku
			FileReader fr;
			try {
				fr = new FileReader(filename);
			} catch (FileNotFoundException ex){
				System.out.println("File "+filename+" cannot be found. Check the --data-filename parameter.");
				return;
			}
			problemObj = new Markowitz();
			try {
				problemObj.loadData(fr);
			} catch (IOException ex){
				System.out.println("File "+filename+" cannot be read properly. Check the data file.");
				return;
			}
			//sprawdzenie dlugosci genotypu - ew. zmniejszenie
			int len = 0;
			if ((len = ((Markowitz)problemObj).getProblemSize()) < genotypeLength){
			
				genotypeLength = len;
				System.out.println("Genotype length reduced to problem size: "+len+".");
			}
		} else if(problem.equalsIgnoreCase("sharpe")) {
			//sprawdzenie istnienia pliku
			FileReader fr;
			try {
				fr = new FileReader(filename);
			} catch (FileNotFoundException ex){
				System.out.println("File "+filename+" cannot be found. Check the --data-filename parameter.");
				return;
			}
			problemObj = new Sharpe();
			try {
				problemObj.loadData(fr);
			} catch (IOException ex){
				System.out.println("File "+filename+" cannot be read properly. Check the data file.");
				return;
			}
			//sprawdzenie dlugosci genotypu - ew. zmniejszenie
			int len = 0;
			if ((len = ((Sharpe)problemObj).getProblemSize()) < genotypeLength){
			
				genotypeLength = len;
				System.out.println("Genotype length reduced to problem size: "+len+".");
			}
			
		} else if(problem.equalsIgnoreCase("capm")) {
			problemObj = new CAPM();
		} else {
			//problemObj = new Markowitz();
			problemObj = new ProblemZDT1();
		}
		
		emas = emas || !(elemas||spea2||nsgaii);;
		IEnvironment environment = null;
		Random generator = new Random();
		if(emas) {
			EvolutionaryEnvironment tmpEnvironment = new EvolutionaryEnvironment(problemObj);
//			tmpEnvironment.
			environment = tmpEnvironment;
			int islands_counter = 0;
			while(islands_counter++<islands) {
				Island tmpIsland = new Island(tmpEnvironment);
				double[] upperBound = new double[genotypeLength];
				double[] lowerBound = new double[genotypeLength];
				int i = 0;
				try {
					while(true) {
						upperBound[i] = problemObj.getUpperBound(i); 
						lowerBound[i] = problemObj.getLowerBound(i); 
						i++;
					}
				} catch (IndexOutOfBoundsException e) {
					//Tu nic nie robimy
				}
				int agents_counter = 0;
				while(agents_counter++<agents) {
					double[] genotype = new double[genotypeLength];
					i = 0;
					try {
						while(true) {
							genotype[i] = generator.nextDouble() * (upperBound[i] - lowerBound[i]) + lowerBound[i];
							i++;
						}
					} catch (IndexOutOfBoundsException e) {
						//Tu nic nie robimy
					}
					
					problemObj.normalize(genotype);
					Agent tmpAgent = new Agent(tmpIsland, problemObj, genotype, initialLifeEnergyLevel, transferableEnergy, whenToDie, whenToReproduce, actionsPropability, mutationParam, generator);
					tmpAgent.giveResource(0, initialLifeEnergyLevel);
					
//					environment.
//					environment.add(tmpAgent);
//					tmpIsland.add(tmpAgent);
				}
			}
			for(Island isl1:tmpEnvironment.getIslands()) {
				for(Island isl2:tmpEnvironment.getIslands()) {
					if(isl1!=isl2) {
						tmpEnvironment.add(new Edge(edge_cost, isl1, isl2));
					}
				}
			}
			
		} else if(elemas) {
//			System.out.println(becomeElitist);
//			System.out.println(distance);
			EvolutionaryEnvironment tmpEnvironment = new EvolutionaryEnvironment(problemObj);
			environment = tmpEnvironment;
			int islands_counter = 0;
			while(islands_counter++<islands) {
				Island tmpIsland = new Island(tmpEnvironment);
				double[] upperBound = new double[genotypeLength];
				double[] lowerBound = new double[genotypeLength];
				int i = 0;
				try {
					while(true) {
						upperBound[i] = problemObj.getUpperBound(i); 
						lowerBound[i] = problemObj.getLowerBound(i); 
						i++;
					}
				} catch (IndexOutOfBoundsException e) {
					//Tu nic nie robimy
				}
				int agents_counter = 0;
				while(agents_counter++<agents) {
					double[] genotype = new double[genotypeLength];
					i = 0;
					try {
						while(true) {
							genotype[i] = generator.nextDouble() * (upperBound[i] - lowerBound[i]) + lowerBound[i];
							i++;
						}
					} catch (IndexOutOfBoundsException e) {
						//Tu nic nie robimy
					}
					problemObj.normalize(genotype);
					ElemasAgent tmpAgent = new ElemasAgent(tmpIsland, problemObj, genotype, initialLifeEnergyLevel, transferableEnergy, whenToDie, whenToReproduce, actionsPropability, mutationParam, generator, becomeElitist, transferableElitarity, distance, initialElitarityLevel);
					tmpAgent.giveResource(0, initialLifeEnergyLevel);
					tmpAgent.giveResource(1, initialElitarityLevel);
//					environment.add(tmpAgent);
//					tmpIsland.add(tmpAgent);
				}
			}
			islands_counter = 0;
			while(islands_counter++<elitistIslands) {
				Island tmpIsland = new Island(tmpEnvironment, true);
				if(elitistAgents==0) continue;
				double[] upperBound = new double[genotypeLength];
				double[] lowerBound = new double[genotypeLength];
				int i = 0;
				try {
					while(true) {
						upperBound[i] = problemObj.getUpperBound(i); 
						lowerBound[i] = problemObj.getLowerBound(i); 
						i++;
					}
				} catch (IndexOutOfBoundsException e) {
					//Tu nic nie robimy
				}
				int agents_counter = 0;
				while(agents_counter++<elitistAgents) {
					double[] genotype = new double[genotypeLength];
					i = 0;
					try {
						while(true) {
							genotype[i] = generator.nextDouble() * (upperBound[i] - lowerBound[i]) + lowerBound[i];
							i++;
						}
					} catch (IndexOutOfBoundsException e) {
						//Tu nic nie robimy
					}
					problemObj.normalize(genotype);
					ElemasAgent tmpAgent = new ElemasAgent(tmpIsland, problemObj, genotype, initialLifeEnergyLevel, transferableEnergy, whenToDie, whenToReproduce, actionsPropability, mutationParam, generator, becomeElitist, transferableElitarity, distance, initialElitarityLevel);
					tmpAgent.giveResource(0, initialLifeEnergyLevel);
				}
			}
			for(Island isl1:tmpEnvironment.getIslands()) {
				for(Island isl2:tmpEnvironment.getIslands()) {
					if(isl1!=isl2) {
						tmpEnvironment.add(new Edge(edge_cost, isl1, isl2));
					}
				}
			}
			for(Island isl1:tmpEnvironment.getIslands(true)) {
				for(Island isl2:tmpEnvironment.getIslands()) {
					tmpEnvironment.add(new Edge(edge_cost, isl2, isl1));
				}
			}
			
		} else if(nsgaii) {
			
			NSGA2Environment tmpEnvironment = new NSGA2Environment(problemObj,mutationParam,mutationProbability,crossoverParam);
			tmpEnvironment.initPopulation(genotypeLength, sizeOfPopulation, sizeOfArchieve);
			
			environment = tmpEnvironment;
			
			
		} else if(spea2) {
			SPEA2Environment tmpEnvironment = new SPEA2Environment(problemObj,mutationParam,mutationProbability,crossoverParam);
			tmpEnvironment.initPopulation(genotypeLength, sizeOfPopulation, sizeOfArchieve);
			
			environment = tmpEnvironment;
		}
		
		int steps_counter = 0;
		//check results
		int check = ((steps-1)/stepIntervals);
		if (check==0)
			check = steps;
		//final results to analyse
		Set<Result>tt = null;
		while(steps_counter<steps) {
			environment.step();
			if(steps_counter % check == 0 || steps_counter+1==steps) {
				
				Set<Result>t = environment.getResults(false);
				printSolution(t, steps_counter+1, steps, false, problemObj);
				tt = environment.getResults(true);
				printSolution(tt, steps_counter+1, steps, true, problemObj);
			}
			//environment.resetResults();
			steps_counter++;
		}
		
		System.out.print(problemObj.performAnalysis(capital,analysisNumber, tt));
		
	}


	public void parse(String[] args) {
		CmdLineParser parser = new CmdLineParser(this);
		parser.setUsageWidth(80);
		
        try {
            parser.parseArgument(args);

        } catch( CmdLineException e ) {
        	
        	System.err.println(e.getMessage());
            return;
        }

	}
	
	public static void printSolution(Set<Result> results, int steps, int allSteps, boolean nonDominated, Problem problem) {
		try {
			String n = problem.toString()+"_"+((emas)?"emas":(spea2?"spea2":nsgaii?"nsga2":"elemas"))+"_"+allSteps+"_"+steps+(nonDominated?"_nonDominated":"");
	        BufferedWriter out = new BufferedWriter(new FileWriter("data/"+n+".plot" ));
	        out.write("set terminal png\n");
	        out.write("set output \"" + n +".png\"\n");
	        out.write("set xrange [0:1]\n");
	        out.write("plot \"" + n + ".dat\" using 1:2 with points pointsize 0.5 title 'Estimated (HVR " + problem.getHVR(results) + ")'");
	        if(problem.getOptimalSolution()!=null) {
	        	out.write(", " + problem.getOptimalSolution() + " title 'Optimal'\n");
	        }
	        out.close();
	        out = new BufferedWriter(new FileWriter("data/"+n+".dat" ));
	        for(Result a:results) {
	        	out.write(a.result[0]+"\t"+a.result[1]+"\n");
	        }
	        out.close();
	    } catch (IOException e) {
	    	e.printStackTrace();
	    }
	}

}
